package org.lora.core.jdbctx;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.sql.DataSource;

import org.apache.dataconn.jdbc.pool.jmx.ConnectionPool;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.log4j.Logger;
import org.lora.core.annotation.Transaction;
import org.lora.core.context.WebContext;
import org.lora.exception.LoraCoreException;
import org.lora.log4j.Log4jUtil;
import org.lora.util.StringUtil;

/**
 * <p>
 * 类名:TransactionSynchronizationManager
 * </p>
 * 描述:<br>
 * 创建时间:2016年12月2日 上午9:44:02<br>
 * 
 * @author Bladnir@outlook.com<br>
 *         修改历史:<br>
 *         2016年12月2日 Bladnir 创建<br>
 */
public class TransactionSynchronizationManager {

	private static final Logger logger = Log4jUtil.getSystemLogger();

	private static final ThreadLocal<Map<String, SqlSession>> sessionMap = new ThreadLocal<>();

	private static final ThreadLocal<Map<String, Connection>> connectionMap = new ThreadLocal<>();

	private static final ThreadLocal<String> transactionName = new ThreadLocal<>();

	private static final ThreadLocal<ExecutorType> executorType = new ThreadLocal<>();

	private static final ThreadLocal<Boolean> isRollBack = new ThreadLocal<>();

	/**
	 * 构造方法
	 */
	private TransactionSynchronizationManager() {
		// just do nothing
	}

	/*****************************************************
	 * 
	 * 事务状态
	 * 
	 *****************************************************/

	private static boolean isHaveTransaction() {
		return StringUtil.isNotNull(transactionName.get());
	}

	/**
	 * 获取事务是否同步
	 * 
	 * 当前有事务，并且已经同步 返回true 其他返回false
	 * 
	 * @param method
	 * @return
	 */
	private static boolean isSynchronizTransaction(Method method) {

		if (!isHaveTransaction()) {
			return false;
		}

		if (method == null) {
			return false;
		}

		return transactionName.get().equals(method.toString());
	}

	/*****************************************************
	 * 
	 * 事务状态
	 * 
	 *****************************************************/

	/*****************************************************
	 * 
	 * 事务的操作
	 * 
	 *****************************************************/

	/**
	 * 开始事务
	 * 
	 * @param method
	 */
	public static void beginTransaction(Method method) {

		if (!isHaveTransaction()) {
			Transaction transactionAnnotation = method.getAnnotation(Transaction.class);
			if (transactionAnnotation != null) {
				transactionName.set(method.toString());
				executorType.set(transactionAnnotation.execType());
			}
		}

	}

	/**
	 * 提交事务
	 * 
	 * @param method
	 */
	public static void commitTransaction(Method method) {

		if (!isHaveTransaction() || isSynchronizTransaction(method)) {
			// commit everything
			commitAllSqlSession();
		}

	}

	/**
	 * 结束事务
	 * 
	 * @param method
	 */
	public static void endTransaction(Method method) {
		if (!isHaveTransaction() || isSynchronizTransaction(method) || isRollback()) {
			// close everything
			closeAllSqlSession();
			clearThreadLocal();
		}
	}

	/**
	 * 回滚事务
	 * 
	 */
	public static void rollbackTransaction() {
		rollbackAllSqlSession();
	}

	/**
	 * 清空ThreadLocal
	 * 
	 */
	public static void clearThreadLocal() {
		sessionMap.remove();
		connectionMap.remove();
		transactionName.remove();
		executorType.remove();
		isRollBack.remove();
	}

	/**
	 * 返回是否回滚标志位
	 * 
	 * @return
	 */
	private static boolean isRollback() {
		if (isRollBack.get() == null) {
			return false;
		} else {
			return isRollBack.get();
		}
	}

	/*****************************************************
	 * 
	 * 事务的操作
	 * 
	 *****************************************************/

	/*****************************************************
	 * 
	 * SqlSession操作
	 * 
	 *****************************************************/

	/**
	 * 获取Mybatis的SqlSession
	 * 
	 * @param environmentId
	 * @return SqlSession
	 * @throws LoraCoreException
	 */
	public static SqlSession getSqlSession(String environmentId) throws LoraCoreException {
		SqlSession result;

		result = getSqlSessionFromSqlSessionMap(environmentId);
		if (result == null) {
			result = openSqlSession(environmentId);
			addSqlSessionToMap(result, environmentId);
		}

		if (result == null) {
			Map<String, SqlSessionFactory> sqlSerssionFactoryMap = WebContext.getInstance().getSqlSessionFactoryMap();
			logger.error("getSqlSessiond Fail environmentId=[" + environmentId + "] sqlSerssionFactoryMap=[" + sqlSerssionFactoryMap + "]");
			throw new LoraCoreException("GetSqlSessiond Fail environmentId=[" + environmentId + "]");
		}

		return result;
	}

	/**
	 * 从threadlocal中获取 SqlSession
	 * 
	 * @param key
	 * @return
	 */
	private static SqlSession getSqlSessionFromSqlSessionMap(String key) {

		Map<String, SqlSession> resourceMap = sessionMap.get();

		if (StringUtil.isNotNull(key) && resourceMap != null && resourceMap.containsKey(key)) {
			return resourceMap.get(key);
		}
		return null;
	}

	/**
	 * 创建新的SqlSession
	 * 
	 * @param environmentId
	 * @return
	 */
	private static SqlSession openSqlSession(String environmentId) {

		SqlSession sqlSession = null;

		ExecutorType execType = executorType.get();
		if (execType == null) {
			execType = ExecutorType.SIMPLE;
		}

		Map<String, SqlSessionFactory> sqlSerssionFactoryMap = WebContext.getInstance().getSqlSessionFactoryMap();
		for (Entry<String, SqlSessionFactory> entry : sqlSerssionFactoryMap.entrySet()) {
			if (entry.getValue().getConfiguration().getEnvironment().getId().equals(environmentId)) {
				sqlSession = entry.getValue().openSession(execType);
				if (logger.isDebugEnabled()) {
					logger.debug("Open SqlSession [" + sqlSession.hashCode() + "]");
				}
				break;
			}
		}

		return sqlSession;

	}

	/**
	 * 添加SqlSession 到ThreadLocal中
	 * 
	 * @param sqlSession
	 * @param environmentId
	 */
	private static void addSqlSessionToMap(SqlSession sqlSession, String environmentId) {

		Map<String, SqlSession> map = sessionMap.get();
		if (map == null) {
			map = new HashMap<>();
		}

		if (StringUtil.isNotNull(environmentId) && !map.containsKey(environmentId)) {
			map.put(environmentId, sqlSession);
		}

		sessionMap.set(map);

	}

	/**
	 * 提交ThreadLocal中的所有 SqlSession
	 * 
	 */
	private static void commitAllSqlSession() {

		Map<String, SqlSession> map = sessionMap.get();
		if (map != null) {

			for (Entry<String, SqlSession> entry : map.entrySet()) {
				entry.getValue().commit();
				if (logger.isDebugEnabled()) {
					logger.debug("Commit SqlSession [" + entry.getValue().hashCode() + "]");
				}
			}

		}

	}

	/**
	 * 关闭ThreadLocal中的所有SqlSession
	 * 
	 * 
	 */
	private static void closeAllSqlSession() {

		Map<String, SqlSession> map = sessionMap.get();
		if (map != null) {

			for (Entry<String, SqlSession> entry : map.entrySet()) {
				entry.getValue().close();
				if (logger.isDebugEnabled()) {
					logger.debug("Close SqlSession [" + entry.getValue().hashCode() + "]");
				}
			}

		}

	}

	/**
	 * 
	 * 回滚ThreadLoca中的所有 SqlSession
	 * 
	 */
	private static void rollbackAllSqlSession() {

		Map<String, SqlSession> map = sessionMap.get();
		if (map != null) {

			for (Entry<String, SqlSession> entry : map.entrySet()) {
				entry.getValue().rollback();
				if (logger.isDebugEnabled()) {
					logger.debug("Rollback SqlSession [" + entry.getValue().hashCode() + "]");
				}
			}

		}
		isRollBack.set(Boolean.TRUE);
	}

	/*****************************************************
	 * 
	 * SqlSession 操作
	 * 
	 *****************************************************/

	/*****************************************************
	 * 
	 * Connection 操作
	 * 
	 *****************************************************/

	/**
	 * 获取Connection
	 * 
	 * @param dataSource
	 * @return Connection
	 */
	public static final Connection getConnection(DataSource dataSource) {

		Connection conn;

		String dataSourceStr = dataSource.toString();

		conn = getConnectionFromMap(dataSourceStr);
		if (conn == null) {
			conn = newConnection(dataSource);
			addConnectionToMap(dataSourceStr, conn);
		}

		return conn;
	}

	/**
	 * 创建新的Connection
	 * 
	 * @param dataSource
	 * @return
	 */
	private static Connection newConnection(DataSource dataSource) {
		Connection conn = null;
		try {
			conn = dataSource.getConnection();
			logger.debug("Open Connection [" + conn.hashCode() + "]" + getPoolInfo());
		} catch (SQLException e) {
			logger.error("Open Connection Error -> " + e.getMessage(), e);
		}

		return conn;
	}

	/**
	 * 保存Connection到ThreadLocal
	 * 
	 * @param key
	 * @param conn
	 */
	private static void addConnectionToMap(String key, Connection conn) {

		Map<String, Connection> map = connectionMap.get();
		if (map == null) {
			map = new HashMap<>();
		}

		if (StringUtil.isNotNull(key) && !map.containsKey(key)) {
			map.put(key, conn);
		}

		connectionMap.set(map);
	}

	/**
	 * 从ThreadLocal中获得Connection
	 * 
	 * @param key
	 * @return
	 */
	private static final Connection getConnectionFromMap(String key) {

		Map<String, Connection> map = connectionMap.get();

		if (map != null && StringUtil.isNotNull(key) && map.containsKey(key)) {
			return map.get(key);
		}

		return null;

	}

	/**
	 * 提交ThreadLocal中的Connection
	 * 
	 */
	public static final void commitConnection() {
		Map<String, Connection> map = connectionMap.get();

		if (map == null) {
			return;
		}

		for (Entry<String, Connection> entry : map.entrySet()) {
			try {
				entry.getValue().commit();
				if (logger.isDebugEnabled()) {
					logger.debug("Commit Connection [" + entry.getValue().hashCode() + "]");
				}
			} catch (SQLException e) {
				logger.error("Commit Connection Error -> " + e.getMessage(), e);
			}
		}
	}

	/**
	 * 关闭ThreadLocal中的Connection
	 * 
	 */
	public static final void closeConnection() {

		Map<String, Connection> map = connectionMap.get();
		if (map == null) {
			return;
		}

		for (Entry<String, Connection> entry : map.entrySet()) {
			try {
				Connection conn = entry.getValue();
				resetAutoCommit(conn);
				conn.close();
				if (logger.isDebugEnabled()) {
					logger.debug("Close Connection [" + entry.getValue().hashCode() + "]" + getPoolInfo());
				}
			} catch (SQLException e) {
				logger.error("Close Connection Error -> " + e.getMessage(), e);
			}
		}

	}

	/**
	 * 回滚ThreadLocal中的Connection
	 * 
	 */
	public static final void rollbackConnection() {

		Map<String, Connection> map = connectionMap.get();
		if (map == null) {
			return;
		}

		for (Entry<String, Connection> entry : map.entrySet()) {
			try {
				entry.getValue().rollback();
				if (logger.isDebugEnabled()) {
					logger.debug("Rollback Connection [" + entry.getValue().hashCode() + "]");
				}
			} catch (SQLException e) {
				logger.error("Rollback Connection Error -> " + e.getMessage(), e);
			}
		}

	}

	/**
	 * 重新设置Connection的autocommit属性
	 * 
	 */
	private static void resetAutoCommit(Connection connection) {
		try {
			if (!connection.getAutoCommit()) {
				// MyBatis does not call commit/rollback on a connection if just
				// selects were performed.
				// Some databases start transactions with select statements
				// and they mandate a commit/rollback before closing the
				// connection.
				// A workaround is setting the autocommit to true before closing
				// the connection.
				// Sybase throws an exception here.
				if (logger.isDebugEnabled()) {
					logger.debug("Resetting autocommit to true on JDBC Connection [" + connection.hashCode() + "]");
				}
				connection.setAutoCommit(true);
			}
		} catch (SQLException e) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error resetting autocommit to true " + "before closing the connection.  Cause: " + e);
			}
		}
	}

	private static String getPoolInfo() {
		Map<String, DataSource> map = WebContext.getInstance().getDataSourceMap();

		StringBuilder sb = new StringBuilder();

		for (Entry<String, DataSource> entry : map.entrySet()) {
			sb.append("DS[").append(entry.getKey()).append("]");

			if (entry.getValue() instanceof org.apache.dataconn.jdbc.pool.DataSource) {

				org.apache.dataconn.jdbc.pool.DataSource ds = (org.apache.dataconn.jdbc.pool.DataSource) entry.getValue();
				org.apache.dataconn.jdbc.pool.ConnectionPool pool = ds.getPool();
				int activeNum = 0;
				int idleNum = 0;
				
				if(pool != null){
					activeNum = pool.getActive();
					idleNum = pool.getIdle();
				}
				
				sb.append("ACT[").append(activeNum).append("]");
				sb.append("IDEL[").append(idleNum).append("]");
			} else {
				sb.append("ACT[empty]");
				sb.append("IDEL[empty]");
			}

		}

		return sb.toString();
	}

	/*****************************************************
	 * 
	 * Connection 操作
	 * 
	 *****************************************************/

}
